Lesson 1 - Design A Poker Game

This notebook will go through all the units of poker game taught at Lesson 1 by Peter Norvig.

Game Rule

Four suits: clubs, diamonds, hearts, spades

Hand Ranking

  1. High card
  2. One pair
  3. Two pair
  4. Three of a kind
  5. Straight
  6. Flush
  7. Full house
  8. Four of a kind
  9. Straight flush
  10. Five of a kind

Wiki


In [47]:
def card_ranks(cards):
    "Return a list of the ranks, sorted with higher first."
    ranks = ["--23456789TJQKA".index(r) for r,s in cards]
    ranks.sort(reverse=True)
    return [5, 4, 3, 2, 1] if (ranks == [14, 5, 4, 3, 2]) else ranks

In [48]:
def straight(ranks):
    "Return True if the ordered ranks form a 5-card straight."
    return (max(ranks) - min(ranks)) == 4 and len(set(ranks)) == 5

In [49]:
def flush(hand):
    "Return True if all the cards have the same suit."
    suits = [card[1] for card in hand]
    return len(set(suits)) == 1

In [50]:
def kind(n, ranks):
    """Return the first rank that this hand has exactly n of.
    Return None if there is no n-of-a-kind in the hand."""
    for rank in ranks:
        if ranks.count(rank) == n: return rank
    return None

In [51]:
def two_pair(ranks):
    """If there are two pair, return the two ranks as a
    tuple: (highest, lowest); otherwise return None."""
    pairs = set([rank for rank in ranks if ranks.count(rank) == 2])
    if len(pairs) != 2:
        return None
    else:
        return (max(pairs), min(pairs))

In [52]:
def hand_rank(hand):
    "Return a value indicating the ranking of a hand."
    ranks = card_ranks(hand)
    if straight(ranks) and flush(hand): # Straight flush
        return (8, max(ranks))
    elif kind(4, ranks): # Four of a kind
        return (7, kind(4, ranks), kind(1, ranks))
    elif kind(3, ranks) and kind(2, ranks): # Full house
        return (6, kind(3, ranks), kind(2, ranks))
    elif flush(hand): # Flush
        return (5, ranks)
    elif straight(ranks): # Straight
        return (4, max(ranks))
    elif kind(3, ranks): # Three of a kind
        return (3, kind(3, ranks), ranks)
    elif two_pair(ranks): # Two pair
        return (2, two_pair(ranks), ranks)
    elif kind(2, ranks): # One pair
        return (1, kind(2, ranks), ranks)
    else: # High card
        return (0, ranks)

In [53]:
def allmax(iterable, key=None):
    "Return a list of all items equal to the max of the iterable."
    key = key or (lambda x: x)
    Max = max(iterable, key=key)
    return [item for item in iterable if key(item) == key(Max)]

In [54]:
def poker(hands):
    "Return a list of winning hands: poker([hand,...]) => [hand,...]"
    return allmax(hands, key=hand_rank)

In [55]:
def test():
    "Test cases for the functions in poker program"
    sf = "6C 7C 8C 9C TC".split()
    sf2 = "6D 7D 8D 9D TD".split()
    fk = "9D 9H 9S 9C 7D".split() 
    fh = "TD TC TH 7C 7D".split()
    tp = "TD TC AD AC 9C".split()
    
    sf_ranks = card_ranks(sf)
    fk_ranks = card_ranks(fk)
    tp_ranks = card_ranks(tp)
    
    # Test cases for card_ranks
    assert card_ranks(['AC', '3D', '4S', 'KH']) == [14, 13, 4, 3]
    assert card_ranks(sf) == [10, 9, 8, 7, 6]
    assert card_ranks(['AC', '2D', '3C', '4D', '5H']) == [5, 4, 3, 2, 1]
    # Test cases straight
    assert straight(sf_ranks) == True
    assert straight(fk_ranks) == False
    # Test cases for flush
    assert flush(sf) == True
    assert flush(fk) == False
    # Test cases for kind
    assert kind(4, fk_ranks) == 9
    assert kind(4, sf_ranks) == None
    # Test cases for two pairs
    assert two_pair(tp_ranks) == (14, 10)
    assert two_pair(fk_ranks) == None
    # Test cases for poker
    assert poker([sf, fk, fh]) == [sf]
    assert poker([fk, fh]) == [fk]
    assert poker([fh, fh]) == [fh, fh]
    assert poker([sf]) == [sf]
    assert poker([sf] + 99*[fh]) == [sf]
    assert poker([sf, sf2, fk]) == [sf, sf2]
    # Test cases for hand_rank
    assert hand_rank(sf) == (8, 10)
    assert hand_rank(fk) == (7, 9, 7)
    assert hand_rank(fh) == (6, 10, 7)
    print "All tests pass!"
test()


All tests pass!

In [56]:
import random

mydeck = [r+s for r in '23456789TJQKA' for s in 'SHDC']
def deal(numhands, n=5, deck=mydeck):
    random.shuffle(mydeck)
    hands = []
    for i in range(numhands):
        hands.append(mydeck[i*n:(i+1)*n])
    return hands

print deal(2)


[['2C', 'JD', 'QH', '7C', 'KS'], ['AH', '9H', '5C', 'QD', '6H']]

In [57]:
from tqdm import tnrange

def hand_percentage(n=1*1000):
    counts = [0]*9
    hand_names = ["High card", "One pair", "Two pair", "Three of a kind", "Straight",
                  "Flush", "Full house", "Four of a kind", "Straight flush"]
    for i in tnrange(n/10):
        for hand in deal(10):
            ranking = hand_rank(hand)[0]
            counts[ranking] += 1
    for i in reversed(range(9)):
        print "{:16}: {:06.3f}".format(hand_names[i], 100.*counts[i]/n)

In [58]:
hand_percentage(100*1000)


Straight flush  : 00.001
Four of a kind  : 00.021
Full house      : 00.136
Flush           : 00.188
Straight        : 00.407
Three of a kind : 02.097
Two pair        : 04.857
One pair        : 42.093
High card       : 50.200

Refactoring


In [59]:
def group(items):
    groups = [(items.count(item), item) for item in set(items)]
    return sorted(groups, reverse=True)

In [60]:
def hand_rank(hand):
    groups = group(["--23456789TJQKA".index(r) for r,s in hand])
    counts, ranks = zip(*groups)
    if ranks == (14, 5, 4, 3, 2):
        ranks = (5, 4, 3, 2, 1)
    isStraight = straight(ranks)
    isFlush = flush(hand)
    return (8 if isStraight and isFlush else
            7 if (4, 1) == counts else
            6 if (3, 2) == counts else
            5 if isFlush else
            4 if isStraight else
            3 if (3, 1, 1) == counts else
            2 if (2, 2, 1) == counts else
            1 if (2, 1, 1, 1) == counts else
            0), ranks

In [61]:
def test():
    "Test cases for the functions in poker program"
    sf = "6C 7C 8C 9C TC".split()
    sf2 = "6D 7D 8D 9D TD".split()
    fk = "9D 9H 9S 9C 7D".split() 
    fh = "TD TC TH 7C 7D".split()
    tp = "TD TC AD AC 9C".split()
    
    # Test cases for group
    assert group([6, 7, 8, 9, 10]) == [(1, 10), (1, 9), (1, 8), (1, 7), (1, 6)]
    assert group([9, 9, 9, 9, 7]) == [(4, 9), (1, 7)]
    assert group([10, 10, 10, 7, 7]) == [(3, 10), (2, 7)]
    # Test cases for group
    assert hand_rank(sf) == (8, (10, 9, 8, 7, 6))
    assert hand_rank(fk) == (7, (9, 7))
    assert hand_rank(fh) == (6, (10, 7))
    assert hand_rank(tp) == (2, (14, 10, 9))
    print "All tests pass!"
test()


All tests pass!

Problems


In [62]:
import itertools
def best_hand(hand):
    """From a more than 5 card hand, return the best 5-card hand."""
    return max(itertools.combinations(hand, 5), key=hand_rank)

In [97]:
allranks = "23456789TJQKA"
Blacks = [x+y for x in allranks for y in "SC"]
Reds = [x+y for x in allranks for y in "DH"]

def best_wild_hand(hand):
    """Try all values for jokers in all 5-card selections."""
    allhands = set(best_hand(set(h)) for h in itertools.product(*map(replacement, hand)) if len(set(h)) >= 5)
    return max(allhands, key=hand_rank)

def replacement(card):
    if card == "?B": return Blacks
    elif card == "?R": return Reds
    else: return [card]

In [ ]: